package com.g001.service;

import java.awt.Dimension;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.particles.ParticleIO;
import org.newdawn.slick.particles.ParticleSystem;
import org.newdawn.slick.state.StateBasedGame;

import com.g001.enums.Animation;
import com.g001.enums.BlockType;
import com.g001.enums.MessagePositionType;
import com.g001.enums.ProxBlockType;
import com.g001.model.BorderBlock;
import com.g001.model.GCharacter;
import com.g001.model.GameBlock;
import com.g001.model.IBlock;
import com.g001.model.Player;
import com.g001.model.GUI.Clock;
import com.g001.model.GUI.MessageManager;
import com.g001.model.powers.AttackAction;
import com.g001.model.powers.CurseAction;
import com.g001.model.powers.HealingAction;
import com.g001.model.powers.MagicAction;
import com.g001.model.powers.MessageAction;
import com.g001.model.powers.ShieldAction;
import com.g001.model.powers.StrikeBackAction;
import com.g001.service.match3.IA.BrainService;
import com.g001.util.BlockUtils;


public class Match3IblockService {
	
	
	List<IBlock> blocks = new ArrayList<IBlock>();
	List<IBlock> topBorderBlocks = new ArrayList<IBlock>();
	List<IBlock> bottomBorderBlocks = new ArrayList<IBlock>();
	IBlock[] selectedBlocks = new IBlock[2];
	// of the Matrix
	Vector2f position = new Vector2f(50,50);
	Dimension arenaSize = new Dimension(1,1);
	
	boolean battleOver = false;
	
	/** the particle system which contains an explosion emitter which we want to duplicate */
	private ParticleSystem particuleSystem;
	
	
	public Animation state;
	
	public List<Animation> animations;
	
	public boolean particuleShow;
	
	Random r = new Random();
	private long timer ;
	
	
	// the players
	private GCharacter player;
	private GCharacter enemy;
	
	// some gui & logic elements
	
	private boolean playerTurn;
	private Clock clock;
	
	private boolean busy;
	private boolean res;
	private BrainService brain;
	private boolean autoFight = false;
	private boolean shuffle;
	
	private MessageManager messageManager;
	

	public void initMatch3Game(GCharacter player, GCharacter enemy) throws SlickException, IOException {
		// load the particle system containing our explosion emitter
		this.particuleSystem = ParticleIO.loadConfiguredSystem("data/endlessexplosion.xml");;
		state = Animation.IDLE;
		String fileName = "data/g001/level3.lvl";
		battleOver = false;
		// set the players
		this.player = player;
		this.enemy = enemy;
		// player starts
		playerTurn = true;
		
		// load game from file ...
		FileInputStream file;
		List<List<IBlock>> bbMain = new ArrayList<List<IBlock>>();
		List<IBlock> bbSub = new ArrayList<IBlock>();
		System.out.println("load from file");
		BlockType bt = null;
		try {
			file = new FileInputStream(this.getClass().getClassLoader().getResource(fileName).getPath());
			Scanner scanner = new Scanner(file);
			String line;
			try {
				  int i = 0;
				  int j = 0;
			      while (scanner.hasNextLine()){
			    	line = scanner.nextLine();
			    	if(!line.startsWith("#")) {
			    		i = 0;
			    		for(String s : line.split(" ")){
			    			
			    			if(s.equals("-")) {
			    				bbSub.add(null);
			    			} else {
				    			bt = BlockType.getBlockType(Integer.parseInt(s));
				    			if(bt == BlockType.BORDER){
				    				BorderBlock borderBlock = new BorderBlock(i, j, position);
				    				bbSub.add(borderBlock);
				    				blocks.add(borderBlock);
				    				if(j == 0)
				    					topBorderBlocks.add(borderBlock);
				    				if(j!=0)
				    					bottomBorderBlocks.add(borderBlock);
				    			} else {
				    				bbSub.add(new GameBlock(bt, i, j, position, particuleSystem));
				    			}
				    			
			    			}
			    			i++;
			    		}
			    		
			    		bbMain.add(bbSub);
			    		bbSub = new ArrayList<IBlock>();
			    		j++;
			    	} else {
			    		line = line.replace('#', ' ');
			    		int w = 0;
			    		int h = 0;
			    		String[] s = line.split("x");
			    		w = Integer.parseInt(s[0].replaceAll(" ", ""));
			    		h = Integer.parseInt(s[1].replaceAll(" ", ""));
			    		position = new Vector2f(w,h);
			    	}
			      }
			      // set the arena size - use full to add new block
			      arenaSize = new Dimension(i,j);
			    } catch (NumberFormatException e) {
					e.printStackTrace();
				} catch (SlickException e) {
					e.printStackTrace();
				}
			    finally{
			      scanner.close();
			      try {
					file.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
			    }
			
						
				resetBlocks();
				
				for(int index = bbMain.size()-1; index>=0; index--) {
					for(IBlock block : bbMain.get(index)){
						if(block instanceof GameBlock)
						addBlock(block);
					}
				}
					
				bbSub = null;
				bbMain = null;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		// time stuff
		clock = new Clock();
		//
		animations = new ArrayList<Animation>();		
		brain = new BrainService();
		messageManager = new MessageManager();
		messageManager.addMessage("Battle Start!", MessageAction.BIG_FONT);
	}
	
	/**
	 * Returns the current player that plays
	 * @return
	 */
	
	private GCharacter getCurrentCharacterPlaying(){
		if(playerTurn){
			return player;
		} else {
			return enemy;
		}
	}
	
	/**
	 * Returns the current opponent of the current player
	 * @return
	 */
	private GCharacter getCurrentOpponent(){
		if(!playerTurn){
			return player;
		} else {
			return enemy;
		}
	}
	
	

	public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
		g.drawString(state.toString(), 0, 0);
		//g.setClip((int)position.x - 50,(int)position.y+BlockType.HEIGHT,BlockType.WIDTH*topBorderBlocks.size(),BlockType.HEIGHT*10+100);
		for(IBlock block : blocks ) {
			if(block!=null && !block.isSelected())
			block.render(container, game, g);
		}
		for(IBlock block : selectedBlocks){
			if(block!=null)
			block.render(container, game, g);
		}
		//g.clearClip();
		particuleSystem.render();
		
		// clock
		clock.render(container, game, g);
		
		//message
		messageManager.render(container, game, g);
		
	}
	
 

	/**
	 * update the clock
	 */
	private void updateTurn(boolean reset){
			if(clock.timeOver() || reset) {
				// all animations are over
				getCurrentCharacterPlaying().startAnimations();
				getCurrentOpponent().startAnimations();
				if(getCurrentCharacterPlaying().isReady() && getCurrentOpponent().isReady())
				{
					if(getCurrentCharacterPlaying() instanceof Player) {
						messageManager.addMessage("Opponent turn!",MessageAction.BIG_FONT);
					} else {
						messageManager.addMessage("Your turn!",MessageAction.BIG_FONT);
					}
					playerTurn = ! playerTurn;
					System.out.println("playerTurn="+playerTurn);
					// update character info
					player.setPlaying(playerTurn);
					enemy.setPlaying(!playerTurn);
					clock.reset();
				}
			}
	}
	
	public void resetBlocksSelection(){
		for(IBlock b  : selectedBlocks) {
			if(b!=null)	b.setSelected(false);
		}
		selectedBlocks = new IBlock[2];
	}
	
	public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
		// clock update
		clock.update(container, game, delta);
		// check that the battle is over or not
		if(player.isDead() || enemy.isDead()){
			state = Animation.BATTLE_OVER;
			if(player.isDead()) {
				System.out.println("YOU LOSE");
				getCurrentCharacterPlaying().addAction(new MessageAction("YOU LOSE!", MessagePositionType.CENTER, 5000));
			} else {
				System.out.println("YOU WIN");
				getCurrentCharacterPlaying().addAction(new MessageAction("YOU WIN!", MessagePositionType.CENTER, 5000));
			}
		}
		
		if(!shuffle) {

		// particules update
		if(particuleShow)
			particuleSystem.update(delta);
		else 
			particuleSystem.reset();
		// needed otheriwse the particles are still visibles ...
		particuleSystem.getEmitter(0).resetState();
		
		// we that block are not busy ...
		busy = false;	
		// blocks update
		addBlock(); 
		for(IBlock block : blocks ) {
			if(block!=null) {
				block.update(container, game, delta);
				if(block.isBusy()) busy = true;
			}
		}
		
		// now doing the stuff
		switch(state) {
		case IDLE : 
			updateTurn(clock.isPlayerAsPlayed());
			manageInputCmd(container);
		break;
		case SWITCH : 
			if(!selectedBlocks[0].isSwitching() && !selectedBlocks[1].isSwitching ()) {
				clock.stop();
				// checks all matches
				if(brain.findMatch(selectedBlocks[0], selectedBlocks[1])){
					// matches!
					state = Animation.MATCH_AUTOMOVE;
				} else {
					// penality to health ! -5
					getCurrentCharacterPlaying().addAction(new CurseAction(getCurrentCharacterPlaying(), getCurrentOpponent()));
					// switch back
					selectedBlocks[0].moveBack(selectedBlocks[1]);
					selectedBlocks[1].moveBack(selectedBlocks[0]);
					brain.switchBlocks(selectedBlocks[1], selectedBlocks[0]);
					state = Animation.IDLE;
				}
				resetBlocksSelection();
			}
			break;
		case MATCH_AUTOMOVE:
			// remove all ref to deleted blocks
			List<IBlock> tempBlocks = new ArrayList<IBlock>(blocks.size());
			boolean oneStillToRemove = false;
			for(IBlock b : blocks) {
				// now we can clean all references
				if(b.isRemove()) {					
					oneStillToRemove = true;
				}
				if(b.isDestroy()) {
					actionOnPlayers(b);
					if(b.getProxyBlock(ProxBlockType.N)!=null){
						b.getProxyBlock(ProxBlockType.N).setProxyBlock(ProxBlockType.S, b.getProxyBlock(ProxBlockType.S));
					}
					b.clearAllProxyBlocks();
				} else {
					tempBlocks.add(b);
				}
			}
			blocks = tempBlocks;
			state = Animation.MATCH_AUTOMOVE;
			if(!oneStillToRemove){
					state = Animation.AUTOSOLVE;
			}
		break;
		
		case AUTOSOLVE :
			state = Animation.MATCH_AUTOMOVE;
			if(autoMove()){
				state = Animation.FALL;
			}
			break;
			
		case FALL:
			state = Animation.IDLE;
			for(IBlock block : blocks){
				if(brain.match(block)) {
					state = Animation.MATCH_AUTOMOVE;
				}
			}
			break;
		case BATTLE_OVER:
			break;
		} // switch
		} else {
			//renew the game !
			System.out.println("SHUFFLE");
			for (IBlock b : blocks) {
				if(b instanceof GameBlock)
				b.setBlockType(BlockType.getRandomBlockType());
			}
			shuffle = false;
		}		
		messageManager.update(container, game, delta);
	}
		
	private void actionOnPlayers(IBlock b) {
		switch(b.getBlockType()){
			case ATTACK:getCurrentCharacterPlaying().addAction(new AttackAction(getCurrentCharacterPlaying(), getCurrentOpponent())) ;break;
			case HEALTH:getCurrentCharacterPlaying().addAction(new HealingAction(getCurrentCharacterPlaying())) ;break;
			case SHIELD:getCurrentCharacterPlaying().addAction(new ShieldAction(getCurrentCharacterPlaying())) ;break;
			case MAGIC_ADD:getCurrentCharacterPlaying().addAction(new MagicAction(getCurrentCharacterPlaying())) ;break;
			case MAGIC_DEL:getCurrentCharacterPlaying().addAction(new CurseAction(getCurrentCharacterPlaying(),getCurrentOpponent())) ;break;
			case STRIKE_BACK:getCurrentCharacterPlaying().addAction(new StrikeBackAction(getCurrentCharacterPlaying(),getCurrentOpponent())) ;break;
		}
	}

	/**
	 * Manage input of Player and Enemy	
	 * @param container
	 * @throws SlickException
	 */
	private void manageInputCmd(GameContainer container) throws SlickException {
		
		if(playerTurn){
			if(autoFight ){
				resetBlocksSelection();
				if(!clock.isPlayerAsPlayed() && clock.isComputerIsReadyToPlay()) {
					// the computer try to find a game to play
					IBlock[] choice = brain.findBestChoiceForCharacter(blocks, player, enemy);
					if(choice!=null) {
						selectedBlocks[0] = BlockUtils.findBlock(choice[0].getId(),blocks);
						selectedBlocks[1] = BlockUtils.findBlock(choice[1].getId(),blocks);
					} else {
						messageManager.addMessage("No more Solution! Shuffle!", MessageAction.BIG_FONT);
						shuffle = true;
					}
					
				}
			} else {
				// real player
			if(container.getInput().isMousePressed(0)){
				int mouseX = container.getInput().getMouseX();
				int mouseY = container.getInput().getMouseY();
				IBlock b = mouseClickAction(mouseX, mouseY);
				if(b!=null && b.getBlockType().isMovable()) {
					if(selectedBlocks[0]==null) {
						selectedBlocks[0] = b;
					} else if(selectedBlocks[1]==null) {
						if(selectedBlocks[0]==b) {
							resetBlocksSelection();
						} else {
							// the selected block is not the same
							if(selectedBlocks[0].isContigousBlock(b)) {
								selectedBlocks[1] = b;
							} else {
								selectedBlocks[0].setSelected(false);
								selectedBlocks[0] = b;
							}
						}
					} else {
						resetBlocksSelection();
					}
				} // b!=null
			} 
			}
		} else {
			resetBlocksSelection();
			if(!clock.isPlayerAsPlayed() && clock.isComputerIsReadyToPlay()) {
				// the computer try to find a game to play
				IBlock[] choice = brain.findBestChoiceForCharacter(blocks, enemy, player);
				if(choice!=null) {
					selectedBlocks[0] = BlockUtils.findBlock(choice[0].getId(),blocks);
					selectedBlocks[1] = BlockUtils.findBlock(choice[1].getId(),blocks);
				} else {
					messageManager.addMessage("No more Solution! Shuffle!", MessageAction.BIG_FONT);
					shuffle = true;
				}
				
			}
		} 
		
		if(selectedBlocks[0]!=null && selectedBlocks[1]!=null) {
			clock.setPlayerAsPlayed(true);
			state = Animation.SWITCH;
			selectedBlocks[0].moveTo(selectedBlocks[1]);
			selectedBlocks[1].moveTo(selectedBlocks[0]);
			brain.switchBlocks(selectedBlocks[0], selectedBlocks[1]);
			// to not allow the pulsing of the selection BUT they are still in the selection table
			for(IBlock block  : selectedBlocks) {
				if(block!=null)	block.setSelected(false);
			}
			
		} // if
		
	}
	
	private boolean autoMove() throws SlickException {
		boolean result = true;
		for(IBlock b : blocks) {
			if(b!=null && !b.isProxyUpdated()){
				b.setRemove(brain.match(b));
				result = false;
			}
		}
		return result;
	}
	
	private IBlock mouseClickAction(final int mouseX, final int mouseY){
		IBlock b = retrievedBlockFromClick(mouseX, mouseY);
		if(b != null){
			if(b.getBlockType().isMovable())
				b.setSelected(!b.isSelected());
			else
				b.setShock();
			b.printSituation();
		}
		return b;
	}
	
	private IBlock retrievedBlockFromClick(int mouseX, int mouseY) {
		for(IBlock b : blocks) {
				if(b!=null && b.isMouseOnIt(mouseX, mouseY)){
					return b;
				};
		}
		return null;
	}
	
	/**
	 * return true if there is still block that can be added
	 * @return
	 * @throws SlickException
	 */
	public boolean addBlock() throws SlickException{
		List<Integer> indexesBorderBlockFree = new ArrayList<Integer>(arenaSize.width);
		if(timer<System.currentTimeMillis()){
			timer = System.currentTimeMillis() + BlockType.BLOCK_SPAWN_TIME;
		
			BorderBlock bb;
			for(IBlock b : topBorderBlocks){
				bb =(BorderBlock)b;
				if(bb.numberOfSouthBlocks(b.getProxyBlock(ProxBlockType.S))<arenaSize.height-1){
					indexesBorderBlockFree.add(b.i);
				}
			}
			if(!indexesBorderBlockFree.isEmpty()) {
				IBlock border = topBorderBlocks.get(indexesBorderBlockFree.get(r.nextInt(indexesBorderBlockFree.size())));
				IBlock south = border.getProxyBlock(ProxBlockType.S);
				GameBlock gameBlock = new GameBlock(BlockType.getRandomBlockType(),  border.i, -2, position, particuleSystem);
				gameBlock.setProxyBlock(ProxBlockType.N, border);
				gameBlock.setProxyBlock(ProxBlockType.S, south);
				blocks.add(gameBlock);
			}
		
		}
		return indexesBorderBlockFree.size()>0;
	}
	
	public void addBlock(IBlock block) throws SlickException{
		IBlock border = topBorderBlocks.get(block.i);
		IBlock south = border.getProxyBlock(ProxBlockType.S);
		block.setProxyBlock(ProxBlockType.N, border);
		block.setProxyBlock(ProxBlockType.S, south);
		blocks.add(block);
	}


	public void resetBlocks() {
		selectedBlocks = new IBlock[2];
		IBlock prev = null;
		for(IBlock block : topBorderBlocks){
			if(block.i % 2 == 0) {
				if(prev!=null){
					block.setProxyBlock(ProxBlockType.SW, prev);
				}
			} else {
				if(prev!=null){
					block.setProxyBlock(ProxBlockType.NW, prev);
				}
			}
			prev = block;
		}
		// bottom
		prev = null;
		for(IBlock block : bottomBorderBlocks){
			if(block.i % 2 == 0) {
				if(prev!=null){
					block.setProxyBlock(ProxBlockType.SW, prev);
				}
			} else {
				if(prev!=null){
					block.setProxyBlock(ProxBlockType.NW, prev);
				}
			}
			prev = block;
		}
		//set S and N
		List<IBlock> newBlocks = new ArrayList<IBlock>(blocks.size());
		for(IBlock b : blocks){
			if(b instanceof BorderBlock){
				newBlocks.add(b);
			} else {
				b.clearAllProxyBlocks();
			}
		}
		for(IBlock b : topBorderBlocks){
			b.setProxyBlock(ProxBlockType.S, bottomBorderBlocks.get(b.i));
		}
		blocks = newBlocks;
	}
	
	
 
 	
	
 
	
	
	
 
	
	
	 
}
